﻿/**
 * FFXIIRCGateway
 *
 * @package ffxiircgateway
 * @version $Id: LogReader.cs 22 2009-05-10 02:17:33Z ayunyan $
 * @author ayunyan <ayu@commun.jp>
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 * @link http://code.google.com/p/ffxiircgateway
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Text;
using System.ComponentModel;

namespace FFXIIRCGateway
{
    public class LogReader
    {
        public delegate void NewLineEventHandler(ChatLine chatLine);
        public event NewLineEventHandler NewLine;

        private volatile ISynchronizeInvoke syncObj;
        private volatile int ChatLogAddr = 0;
        private volatile int ChatBaseAddr = 0;
        private volatile bool shouldStop = false;
        private volatile bool readThreadRunning = false;
        private volatile Memory memory;
        private Thread readThread;

        public LogReader(Memory memory)
        {
            this.memory = memory;
        }

        public LogReader(Memory memory, ISynchronizeInvoke owner)
        {
            this.memory = memory;
            this.syncObj = owner;
        }

        public bool Start()
        {
            if (this.readThreadRunning) return false;
            try
            {
                this.ChatLogAddr = this.memory.searchAddress("8B-0D", "85-C9-74-0F");
                this.readThread = new Thread(ReadThread);
                this.readThread.Start();
            }
            catch
            {
                return false;
            }
            return true;
        }

        public void Stop()
        {
            this.shouldStop = true;
            this.readThread.Join();
        }

        private void ReadThread()
        {
            int lastLine = 0;
            this.readThreadRunning = true;
            while (!this.shouldStop)
            {
                if (this.ChatBaseAddr == 0)
                {
                    this.ChatBaseAddr = this.memory.readInt32(ChatLogAddr + 4);
                    Thread.Sleep(100);
                    continue;
                }
                int lineCount = 0;
                int logStart = 0;
                byte[] offsetBytes = new Byte[258];
                byte[] logOffsets = this.memory.readBytes(this.ChatBaseAddr, 258);
                lineCount = logOffsets[240];
                logStart = BitConverter.ToInt32(logOffsets, 244);
                offsetBytes = this.memory.readBytes(BitConverter.ToInt32(logOffsets, 4), 100);

                List<short> lineOffsets = new List<short>();
                lineOffsets.Add(0);
                int startIndex = 2;
                do
                {
                    short offset = BitConverter.ToInt16(offsetBytes, startIndex);
                    if (offset == 0) break;
                    lineOffsets.Add(offset);
                    startIndex += 2;
                } while (startIndex <= 98);


                int loopStart = 0;
                int loopEnd = 0;
                if (lastLine > lineCount)
                {
                    loopStart = lastLine;
                    loopEnd = 49;
                }
                else
                {
                    loopStart = lastLine;
                    loopEnd = lineCount - 1;
                }

                for (int i = loopStart; i <= loopEnd; i++)
                {
                    try
                    {
                        byte[] line = this.memory.readBytes(logStart + lineOffsets[i], 200);
                        string lineString = Encoding.Default.GetString(line);
                        string[] arr = lineString.Split(new char[] { ',' }, 12);
                        if (arr.Length != 12) continue;
                        ChatLine chatLine = new ChatLine();
                        chatLine.type = arr[0];
                        chatLine.unknown1 = arr[1];
                        chatLine.unknown2 = arr[2];
                        chatLine.color = arr[3];
                        chatLine.eventSeq = int.Parse(arr[4], NumberStyles.AllowHexSpecifier);
                        chatLine.uniqueSeq = int.Parse(arr[5], NumberStyles.AllowHexSpecifier);
                        chatLine.unknown3 = arr[6];
                        chatLine.unknown4 = arr[7];
                        chatLine.unknown5 = arr[8];
                        chatLine.unknown6 = arr[9];
                        chatLine.category = arr[10];

                        byte[] bytes = Encoding.Default.GetBytes(arr[11]);

                        Array.Copy(bytes, 4, bytes, 0, bytes.Length - 4); // 開始コード 0x1e 0x01 0x1e 0x01
                        if (bytes[0] == 0x1f && bytes[1] == 0x79)
                        { // 合成の時だけ？ 0x1f 0x79
                            Array.Copy(bytes, 2, bytes, 0, bytes.Length - 2);
                        }

                        for (int j = 0; j <= bytes.Length; j++)
                        {
                            if (bytes[j] == 0)
                            {
                                if (bytes[j - 2] == 0x7f && bytes[j - 1] == 0x31) j -= 2; // 終了コード(オプション)
                                Array.Resize<byte>(ref bytes, j);
                                break;
                            }
                            // アイテムマークコード 0x1e 0x02, 0x1e 0x01, 0x1e 0x03
                            else if (bytes[j] == 0x1e && bytes.Length >= j + 2 && (bytes[j + 1] == 0x03 || bytes[j + 1] == 0x02 || bytes[j + 1] == 0x01))
                            {
                                Array.Copy(bytes, j + 2, bytes, j, bytes.Length - (j + 2));
                            }
                        }
                        chatLine.text = Encoding.Default.GetString(bytes);
                        OnNewLine(chatLine);
                    }
                    catch
                    {
                    }
                }

                if (lastLine > lineCount)
                {
                    lastLine = 0;
                }
                else
                {
                    lastLine = lineCount;
                }

                Thread.Sleep(50);
            }
            this.readThreadRunning = false;
        }

        protected virtual void OnNewLine(ChatLine chatLine)
        {
            if (this.NewLine != null)
            {
                if (this.syncObj != null && this.syncObj.InvokeRequired)
                {
                    this.syncObj.Invoke(NewLine, new object[] { chatLine });
                }
                else
                {
                    this.NewLine(chatLine);
                }
            }
        }
    }

    public class ChatLine
    {
        public string type;
        public string unknown1;
        public string unknown2;
        public string color;
        public int eventSeq;
        public int uniqueSeq;
        public string unknown3;
        public string unknown4;
        public string unknown5;
        public string unknown6;
        public string category;
        public string text;
    }
}
